<?php

namespace Template\Compiler;

abstract class ACompiler {

    private $filename = null;

    protected function getFilename () {
        return $this->filename;
    }

    protected function getData () {
        return file_get_contents($this->getFilename());
    }

    private $parser = null;

    protected function getParser () {
        if ($this->parser === null) {
            $this->parser = xml_parser_create();
            xml_parser_set_option($this->parser, XML_OPTION_CASE_FOLDING,    1);
            xml_parser_set_option($this->parser, XML_OPTION_SKIP_TAGSTART,   0);
            xml_parser_set_option($this->parser, XML_OPTION_SKIP_WHITE,      1);
            xml_parser_set_option($this->parser, XML_OPTION_TARGET_ENCODING, 'UTF-8');
        }
        return $this->parser;
    }

    private $transformer = null;

    protected function getTransformer () {
        if ($this->transformer === null) {
            $this->transformer = new \CTransformer();
        }
        return $this->transformer;
    }

    public function __construct ($filename) {
        return $this->filename = (string) $filename;
    }

    public function compile (array $options = null) {
        $content = array();
        $elements = array();
        $latest = null;
        $transformer = $this->getTransformer();
        xml_set_element_handler($this->getParser(),
            function ($parser , $name , array $attributes) use (&$elements, &$transformer, &$content, &$latest) {
                $name = strtolower($name);
                $attributes = call_user_func(function ($attributes) {
                    $result = array();
                    foreach ($attributes as $name => $value) {
                        $name = strtolower($name);
                        $result[$name] = $value;
                    }
                    return $result;
                }, $attributes);

                if ($name === 'tpl:templates') {
                    if (!empty($elements)) {
                        throw new \Exception('');
                    }
                    call_user_func(function () use (&$content, &$transformer, $name, $attributes) {
                        // TODO: получать пространство имен из файловой системы !!!
                        $namespace = array('User', 'Profile');
                        $extends = array();
                        if (isset($attributes['extends'])) {
                            // TODO: проверять, что такой шаблон вообще существует !!!
                            if (!preg_match('#^[a-z](?:[a-z0-9-]*[a-z0-9])?(/[a-z](?:[a-z0-9-]*[a-z0-9])?)*$#', $attributes['extends'])) {
                                throw new \Exception('');
                            }
                            $extends = array_map(function ($value) {
                                $value = preg_replace_callback('#-[a-z]#', function ($matches) {
                                    return strtoupper(substr($matches[0], 1, 1));
                                }, $value);
                                return ucfirst($value);
                            }, explode('/', $attributes['extends']));
                        }
                        $transformer->createClassOpen($content, $namespace, $extends);
                    });
                } elseif ($name === 'tpl:template') {
                    if (end($elements) !== 'tpl:templates') {
                        throw new \Exception('Тег шаблонов \'tpl:template\' может содержаться только в блоке \'tpl:templates\'.');
                    }
                    call_user_func(function () use (&$content, &$transformer, $name, $attributes) {
                        if (!isset($attributes['name'])) {
                            throw new \Exception('Тег шаблона \'tpl:template\' должен содержать название (атрибут name).');
                        }
                        if (!preg_match('#^[a-z](?:[a-z0-9-]*[a-z0-9])?$#', $attributes['name'])) {
                            throw new \Exception('Не верно задано название шаблона.');
                        }
                        $name = preg_replace_callback('#-[a-z]#', function ($matches) {
                            return strtoupper(substr($matches[0], 1, 1));
                        }, $attributes['name']);
                        $transformer->createTemplateOpen($content, ucfirst($name));
                    });
                } elseif ($name === 'tpl:param') {
                    if (end($elements) !== 'tpl:call' && end($elements) !== 'tpl:template') {
                        throw new \Exception('Параметры можно использовать только внутри \'tpl:call\' или \'tpl:template\'.');
                    }
                    if ($latest !== 'tpl:call' && $latest !== 'tpl:template' && $latest !== 'tpl:param') {
                        throw new \Exception('Параметры необходимо указывать первыми в блоке.');
                    }
                    if (end($elements) === 'tpl:template') {
                        call_user_func(function () use (&$content, &$transformer, $name, $attributes) {
                            if (!isset($attributes['name'])) {
                                throw new \Exception('Тег параметра \'tpl:param\' должен содержать название (атрибут name).');
                            }
                            // TODO: проверять правильность написания названия переменной.
                            $transformer->createTemplateParam($content, $attributes['name'],
                                isset($attributes['type']) ? $attributes['type'] : null,
                                isset($attributes['default']) ? $attributes['default'] : null);
                        });
                    }
                } elseif (end($elements) === 'tpl:templates') {
                    throw new \Exception('В блоке \'tpl:templates\' могут содержаться только теги шаблонов.');
                }

                array_push($elements, $name);
                $latest = $name;
            },
            function ($parser , $name) use (&$elements, &$transformer, &$content) {
                $name = strtolower($name);

                if ($name === 'tpl:templates') {
                    $transformer->createClassClose($content);
                } elseif ($name === 'tpl:template') {
                    $transformer->createTemplateClose($content);
                }

                array_pop($elements);
            });
        xml_set_character_data_handler($this->getParser(),
            function ($parser, $data) use (&$elements) {
            });

        xml_parse($this->getParser(), $this->getData(), true);
        echo implode("\n", $content);

    }

    public static function create ($filename) {
        return new static($filename);
    }

}
